﻿using System;
using System.IO;
using System.Linq;
using System.Diagnostics;
using System.Collections.Generic;
using System.Reflection;
using System.Timers;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BITS.PreCompiler.CSharp.Tests.Syntax
{
    [TestClass]
    public abstract class TestBase
    {
        # region Constants

        public const String TOTAL_TIME = "TotalTime";

        # endregion

        # region Fields & Properties

        public Stack<String> WatchStack { get; set; }

        public TestContext TestContext { get; set; }

        public Assembly Assembly
        {
            get { return Assembly.GetAssembly(this.GetType()); }
        }

        public String ResourceUri
        {
            get { return this.Assembly.GetName().Name; }
        }

        protected IDictionary<String, TimeStopper> Times;

        # endregion

        # region Public Methods

        public Stream GetResourceStream(String item)
        {
            var uri = this.ResourceUri + "." + item.Replace('\\', '.');
            var stream = this.Assembly.GetManifestResourceStream(uri);

            if (stream == null)
                throw new InternalTestFailureException(String.Format("Resource stream: {0} not found.", uri));

            return stream;
        }

        public String GetResource(String item)
        {
            return new StreamReader(this.GetResourceStream(item)).ReadToEnd();
        }

        public Stopwatch StartWatch(String key)
        {
            return this.StartWatch(key, false);
        }

        public Stopwatch StartWatch(String key, Boolean write)
        {
            return this.StartWatch(key, -1, write);
        }

        public Stopwatch StartWatch(String key, Int32 count)
        {
            return this.StartWatch(key, count, false);
        }

        public Stopwatch StartWatch(String key, Int32 count, Boolean write)
        {
            var newKey = key;

            if (count >= 0)
                newKey = String.Format("[{0}]{1}", count, key);

            if (this.Times.ContainsKey(newKey))
            {
                throw new InternalTestFailureException(String.Format(
                    "Key:{0} already exists. Use an other key name.", newKey));
            }

            var level = this.WatchStack.Count;
            this.WatchStack.Push(newKey);
            var stopWath = new TimeStopper
            {
                Key         = key,
                Level       = level,
                IsCountable = count >= 0,
                IsWriteable = write,
            };

            this.Times[newKey] = stopWath;
            stopWath.Start();

            return stopWath;
        }

        public Stopwatch StopWatch()
        {
            var key = this.WatchStack.Pop();

            if (!this.Times.ContainsKey(key))
            {
                throw new InternalTestFailureException(String.Format(
                    "Key:{0} does not exists exists", key));
            }

            var stopWatch = this.Times[key];
            stopWatch.Stop();

            return stopWatch;
        }

        protected void WriteTimes()
        {
            var writeResults = this.Times.Where(x => x.Value.IsWriteable);

            foreach (var item in writeResults)
            {
                var key  = item.Key;
                var time = item.Value;

                Console.WriteLine(String.Format("{0}[{1}] - {2}ms",
                    new String('-', time.Level), key, time.ElapsedMilliseconds));
            }

            var avgResults = from t in this.Times.Values
                             where t.IsCountable
                             group t by t.Key into cat
                             select new
                             {
                                 cat.Key,
                                 Count = cat.Count(),
                                 Avg   = cat.Average(t => t.ElapsedMilliseconds)
                             };

            if (writeResults.Count() > 0 && avgResults.Count() > 0)
            {
                Console.WriteLine();
                Console.WriteLine("Avarage Times:");
            }

            foreach (var cat in avgResults)
            {
                Console.WriteLine(String.Format("[{0}] - Avg:{1}ms, Count:{2}",
                    cat.Key, cat.Avg, cat.Count));
            }
        }

        # endregion

        # region TestSetup

        [TestInitialize]
        [Attributes.TestInitialize]
        public virtual void TestInitialize()
        {
            this.Times = new Dictionary<String, TimeStopper>();
            this.WatchStack = new Stack<String>();
            this.StartWatch(TOTAL_TIME, true);

            var timer = new Timer(1000 * 60 * 5);

            timer.Elapsed += (sender, args) =>
            {
                try
                {
                    Environment.Exit(-1);
                }
                finally
                {
                    Process.GetCurrentProcess().Kill();
                }
            };

            //timer.Start();
        }

        [TestCleanup]
        [Attributes.TestCleanup]
        public virtual void TestCleanup()
        {
            this.StopWatch();
            this.WriteTimes();
        }

        # endregion

        # region Inner Classe's

        public class TimeStopper : Stopwatch
        {
            public String Key { get; set; }

            public Int32 Level { get; set; }

            public Boolean IsCountable { get; set; }

            public Boolean IsWriteable { get; set; }
        }

        # endregion
    }
}
